// Go作者版权所有。版权所有。
// 此源代码的使用受BSD样式的约束
// 可以在许可证文件中找到的许可证。

package syntax

import "fmt"

// ToDO（GRI）考虑将这部分解析器代码

// CheckBranchs检查标签和分支的正确使用
// 函数体中的语句（break、continue、goto）。
// 它抓住了：
// -错放的中断和继续
// -坏标签的中断和继续
// -无效、未使用、重复和缺少标签
// -gotos跳转到变量声明和块中
func checkBranches(body *BlockStmt, errh ErrorHandler) {
	if body == nil {
		return
	}

	// 此正文中所有标签的范围
	ls := &labelScope{errh: errh}
	fwdGotos := ls.blockBranches(nil, targets{}, nil, body.Pos(), body.List)

	// 如果还剩下任何正向转到，则未找到匹配的标签
	// 为他们找到了。要么这些标签从未定义，要么
	// 它们位于街区内，无法从gotos到达。
	for _, fwd := range fwdGotos {
		name := fwd.Label.Value
		if l := ls.labels[name]; l != nil {
			l.used = true // 避免“已定义且未使用”错误
			ls.err(fwd.Label.Pos(), "goto %s jumps into block starting at %s", name, l.parent.start)
		} else {
			ls.err(fwd.Label.Pos(), "label %s not defined", name)
		}
	}

	// 说明：“定义从未使用过的标签是非法的。”
	for _, l := range ls.labels {
		if !l.used {
			l := l.lstmt.Label
			ls.err(l.Pos(), "label %s defined and not used", l.Value)
		}
	}
}

type labelScope struct {
	errh   ErrorHandler
	labels map[string]*label // 函数内部的所有标签声明；懒散地分配
}

type label struct {
	parent *block       // 包含此标签声明的块
	lstmt  *LabeledStmt // 声明标签的语句
	used   bool         // 是否使用标签
}

type block struct {
	parent *block       // 立即封闭块，或零
	start  Pos          // 起跑
	lstmt  *LabeledStmt // 与此块关联的带标签语句，或nil
}

func (ls *labelScope) err(pos Pos, format string, args ...interface{}) {
	ls.errh(Error{pos, fmt.Sprintf(format, args...)})
}

// declare声明由块b中的s引入的标签并返回
// 新标签。如果标签已声明，请声明报告
// 和错误，并返回现有标签。
func (ls *labelScope) declare(b *block, s *LabeledStmt) *label {
	name := s.Label.Value
	labels := ls.labels
	if labels == nil {
		labels = make(map[string]*label)
		ls.labels = labels
	} else if alt := labels[name]; alt != nil {
		ls.err(s.Label.Pos(), "label %s already defined at %s", name, alt.lstmt.Label.Pos().String())
		return alt
	}
	l := &label{b, s, false}
	labels[name] = l
	return l
}

// gotoTarget返回与给定名称和
// 在块b或其任何封闭块中声明。结果是零
// 如果标签未定义，或与有效的带标签语句不匹配。
func (ls *labelScope) gotoTarget(b *block, name string) *LabeledStmt {
	if l := ls.labels[name]; l != nil {
		l.used = true // 即使它不是一个有效的目标
		for ; b != nil; b = b.parent {
			if l.parent == b {
				return l.lstmt
			}
		}
	}
	return nil
}

var invalid = new(LabeledStmt) // singleton向无效的封闭目标发送信号

// EnclosuringTarget返回最内层的封闭标记语句匹配
// 名字。如果未定义标签，则结果为零，并且无效
// 如果已定义标签，但未标记有效的已标记语句。
func (ls *labelScope) enclosingTarget(b *block, name string) *LabeledStmt {
	if l := ls.labels[name]; l != nil {
		l.used = true // 即使它不是有效的目标（例如，请参阅test/fixedbug/bug136.go）
		for ; b != nil; b = b.parent {
			if l.lstmt == b.lstmt {
				return l.lstmt
			}
		}
		return invalid
	}
	return nil
}

// targets描述中断的目标语句
// 或continue语句是有效的。
type targets struct {
	breaks    Stmt     // *对于TMT，*开关STMT，*选择STMT或nil
	continues *ForStmt // 还是零
}

// BlockBranchs从开始处理块体并返回
// 未解析（转发）GOTO的列表。父对象是直接封闭的对象
// 块（或nil），ctxt提供有关封闭语句的信息，
// lstmt是与此块关联的带标签语句，或nil。
func (ls *labelScope) blockBranches(parent *block, ctxt targets, lstmt *LabeledStmt, start Pos, body []Stmt) []*BranchStmt {
	b := &block{parent: parent, start: start, lstmt: lstmt}

	var varPos Pos
	var varName Expr
	var fwdGotos, badGotos []*BranchStmt

	recordVarDecl := func(pos Pos, name Expr) {
		varPos = pos
		varName = name
		// 任何现有的向前转到跳过变量
		// 声明无效。后藤可能还会跳出来
		// 我们还不知道。
		// 记住所有的前向转机都是潜在的坏转机。
		badGotos = append(badGotos[:0], fwdGotos...)
	}

	jumpsOverVarDecl := func(fwd *BranchStmt) bool {
		if varPos.IsKnown() {
			for _, bad := range badGotos {
				if fwd == bad {
					return true
				}
			}
		}
		return false
	}

	innerBlock := func(ctxt targets, start Pos, body []Stmt) {
		// 来自内部块的未解析前向GOTO
		// 成为当前块的正向转到。
		fwdGotos = append(fwdGotos, ls.blockBranches(b, ctxt, lstmt, start, body)...)
	}

	for _, stmt := range body {
		lstmt = nil
	L:
		switch s := stmt.(type) {
		case *DeclStmt:
			for _, d := range s.DeclList {
				if v, ok := d.(*VarDecl); ok {
					recordVarDecl(v.Pos(), v.NameList[0])
					break // 第一个VarDecl就行了
				}
			}

		case *LabeledStmt:
			// 声明非空白标签
			if name := s.Label.Value; name != "_" {
				l := ls.declare(b, s)
				// 解析匹配的前向goto
				i := 0
				for _, fwd := range fwdGotos {
					if fwd.Label.Value == name {
						fwd.Target = s
						l.used = true
						if jumpsOverVarDecl(fwd) {
							ls.err(
								fwd.Label.Pos(),
								"goto %s jumps over declaration of %s at %s",
								name, String(varName), varPos,
							)
						}
					} else {
						// 没有对手-继续前进
						fwdGotos[i] = fwd
						i++
					}
				}
				fwdGotos = fwdGotos[:i]
				lstmt = s
			}
			// 进程标记语句
			stmt = s.Stmt
			goto L

		case *BranchStmt:
			// 未标记分支语句
			if s.Label == nil {
				switch s.Tok {
				case _Break:
					if t := ctxt.breaks; t != nil {
						s.Target = t
					} else {
						ls.err(s.Pos(), "break is not in a loop, switch, or select")
					}
				case _Continue:
					if t := ctxt.continues; t != nil {
						s.Target = t
					} else {
						ls.err(s.Pos(), "continue is not in a loop")
					}
				case _Fallthrough:
					// 无事可做
				case _Goto:
					fallthrough // 应该始终有一个标签
				default:
					panic("invalid BranchStmt")
				}
				break
			}

			// 标记分支语句
			name := s.Label.Value
			switch s.Tok {
			case _Break:
				// 说明：“如果有标签，它必须是封闭的
				// “for”、“switch”或“select”语句，即
				// 其执行终止。”
				if t := ls.enclosingTarget(b, name); t != nil {
					switch t := t.Stmt.(type) {
					case *SwitchStmt, *SelectStmt, *ForStmt:
						s.Target = t
					default:
						ls.err(s.Label.Pos(), "invalid break label %s", name)
					}
				} else {
					ls.err(s.Label.Pos(), "break label not defined: %s", name)
				}

			case _Continue:
				// 说明：“如果有标签，它必须是封闭的
				// “for”语句，该语句的执行将提前
				if t := ls.enclosingTarget(b, name); t != nil {
					if t, ok := t.Stmt.(*ForStmt); ok {
						s.Target = t
					} else {
						ls.err(s.Label.Pos(), "invalid continue label %s", name)
					}
				} else {
					ls.err(s.Label.Pos(), "continue label not defined: %s", name)
				}

			case _Goto:
				if t := ls.gotoTarget(b, name); t != nil {
					s.Target = t
				} else {
					// 标签可在以后声明-将转到添加到转发转到
					fwdGotos = append(fwdGotos, s)
				}

			case _Fallthrough:
				fallthrough // 不应该有标签
			default:
				panic("invalid BranchStmt")
			}

		case *AssignStmt:
			if s.Op == Def {
				recordVarDecl(s.Pos(), s.Lhs)
			}

		case *BlockStmt:
			innerBlock(ctxt, s.Pos(), s.List)

		case *IfStmt:
			innerBlock(ctxt, s.Then.Pos(), s.Then.List)
			if s.Else != nil {
				innerBlock(ctxt, s.Else.Pos(), []Stmt{s.Else})
			}

		case *ForStmt:
			innerBlock(targets{s, s}, s.Body.Pos(), s.Body.List)

		case *SwitchStmt:
			inner := targets{s, ctxt.continues}
			for _, cc := range s.Body {
				innerBlock(inner, cc.Pos(), cc.Body)
			}

		case *SelectStmt:
			inner := targets{s, ctxt.continues}
			for _, cc := range s.Body {
				innerBlock(inner, cc.Pos(), cc.Body)
			}
		}
	}

	return fwdGotos
}
